﻿using System;
using System.Data;
using System.Data.Entity.Design.PluralizationServices;
using System.Data.Objects.DataClasses;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Configuration;
using System.Collections;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Text.RegularExpressions;

namespace Citi.GocAttributes.Domain.Utils
{
    public class GeneralAction : EntityObject
    {
        #region Public Properties

        protected const string ErrorFieldNull = "The field '{0}' can not be null";
        public string ConnectionString;
        private static EntityObject Entity { get; set; }
        private static bool IsPluralized { get; set; }
        private const string Active = "Active";
        private const string Id = "Id";

        #endregion

        #region Constructor
        /// <summary>
        /// General methods for Entity Framework. Default parent class entity and pluralization true.
        /// </summary>
        public GeneralAction()
        {
            Entity = this;
            IsPluralized = true;
            Context.ConnectionString = ConfigurationManager.ConnectionStrings[typeof(GocAttributesEntities).Name].ConnectionString;
        }

        public GeneralAction(string connectionString)
        {
            Entity = this;
            IsPluralized = true;
            Context.ConnectionString = connectionString;
        }

        /// <summary>
        /// General methods for Entity Framework. Default pluralization true.
        /// </summary>
        /// <param name="entity">Local entity</param>
        public GeneralAction(EntityObject entity)
        {
            Entity = entity;
            IsPluralized = true;
            Context.ConnectionString = ConfigurationManager.ConnectionStrings[typeof(GocAttributesEntities).Name].ConnectionString;
        }

        /// <summary>
        /// General methods for Entity Framework.
        /// </summary>
        /// <param name="entity">Local entity</param>
        /// <param name="isPluralized">Model is pluralized</param>
        public GeneralAction(EntityObject entity, bool isPluralized)
        {
            Entity = entity;
            IsPluralized = isPluralized;
            Context.ConnectionString = ConfigurationManager.ConnectionStrings[typeof(GocAttributesEntities).Name].ConnectionString;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Saves object in database. 
        /// If EntityState is Added or Detached will be add on database.
        /// If EntityState is Unchanged will return and not accessing database
        /// If EntityState is Deleted will be delete on database
        /// If EntityState is Modified will be change on database
        /// </summary>
        public void Save()
        {
            //Context.ClearContext();

            switch (GetEntityState())
            {
                case EntityState.Added:
                case EntityState.Detached:
                    Context.Instance.AddObject(GetEntityName(Entity.GetType()), Entity);
                    break;
                case EntityState.Unchanged:
                    return;
                case EntityState.Deleted:
                    Delete();
                    return;
            }

            try
            {
                Context.SaveChanges();
            }
            catch (UpdateException)
            {
                Context.Instance.Detach(Entity);
                throw;
            }
        }

        public void Save<T>(List<T> entities, int batch) where T : GeneralAction
        {
            var entityName = IsPluralized
                   ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(typeof(T).Name)
                   : typeof(T).Name;


            foreach (var entity in entities)
            {
                try
                {
                    switch (entity.EntityState)
                    {
                        case EntityState.Added:
                        case EntityState.Detached:
                            Context.Instance.AddObject(entityName, entity);
                            break;
                        case EntityState.Unchanged:
                            continue;
                        case EntityState.Deleted:
                            //Delete();
                            continue;
                    }

                    if (entities.IndexOf(entity) % batch != 0 && entities.IndexOf(entity) != entities.Count() - 1)
                        continue;

                    //Context.ClearAddedInContext();
                    Context.SaveChanges();
                }
                catch (InvalidOperationException)
                {
                    //Context.Instance.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                    //Context.Instance.CreateObjectSet<T>().ApplyCurrentValues(entity);
                    Context.Instance.Attach(entity);
                    Context.SaveChanges();
                }
                catch (UpdateException)
                {
                    Context.Instance.Detach(entity);
                    throw;
                }
            }
        }

        //public void Save<T>(List<T> entities, int batch) where T : GeneralAction
        //{
        //    var entityName = IsPluralized
        //           ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(typeof(T).Name)
        //           : typeof(T).Name;

        //    //Context.ClearAddedInContext();
        //    using (var tempContext = new GocAttributesEntities())
        //    {
        //        var propertyInfo = typeof(T).GetProperty("EntityState");
        //        entities.RemoveAll(x => x == null);

        //        foreach (var entity in entities)
        //        {
        //            if (((int)entity.GetProperty(entity, Id) != 0))
        //            {
        //                try
        //                {
        //                    if (((int)entity.GetProperty(entity, Id) != 0 && entity.EntityState != EntityState.Detached))
        //                    {
        //                        var stateEntry = Context.Instance.ObjectStateManager.GetObjectStateEntry(entity);
        //                        var properties = stateEntry.GetModifiedProperties().ToList();
        //                        Context.Instance.Detach(entity);

        //                        tempContext.Attach(entity);

        //                        var state = tempContext.ObjectStateManager.GetObjectStateEntry(entity);
        //                        //state.SetModified();

        //                        foreach (var property in properties)
        //                            state.SetModifiedProperty(property);
        //                    }
        //                    else
        //                    {

        //                    }
        //                }
        //                catch { }
        //            }
        //            else if (entity.EntityState == EntityState.Added)
        //            {
        //                try
        //                {
        //                    Context.Instance.Detach(entity);
        //                }
        //                catch { }
        //            }

        //            var entityState = (EntityState)propertyInfo.GetValue(entity, null);

        //            try
        //            {
        //                switch (entityState)
        //                {
        //                    case EntityState.Added:
        //                    case EntityState.Detached:
        //                        tempContext.AddObject(entityName, entity);
        //                        break;
        //                    case EntityState.Unchanged:
        //                        continue;
        //                    case EntityState.Deleted:
        //                        //Delete();
        //                        continue;
        //                }

        //                if (entities.IndexOf(entity) % batch != 0 && entities.IndexOf(entity) != entities.Count() - 1)
        //                    continue;

        //                //Context.ClearAddedInContext();
        //                tempContext.SaveChanges();
        //            }
        //            catch (InvalidOperationException)
        //            {
        //                //Context.Instance.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
        //                //Context.Instance.CreateObjectSet<T>().ApplyCurrentValues(entity);
        //                tempContext.Attach(entity);
        //                tempContext.SaveChanges();
        //            }
        //            catch (UpdateException)
        //            {
        //                tempContext.Detach(entity);
        //                throw;
        //            }
        //        }
        //    }
        //}



        public void Save<T>(T entity) where T : GeneralAction
        {
            var entityName = IsPluralized
                    ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(entity.GetType().Name)
                    : entity.GetType().Name;

            PropertyInfo propertyInfo = entity.GetType().GetProperty("EntityState");
            var entityState = (EntityState)propertyInfo.GetValue(entity, null);

            try
            {
                switch (entityState)
                {
                    case EntityState.Added:
                    case EntityState.Detached:
                        Context.Instance.AddObject(entityName, entity);
                        break;
                    case EntityState.Unchanged:
                        return;
                    case EntityState.Deleted:
                        Delete();
                        return;
                }

                Context.SaveChanges();
            }
            catch (InvalidOperationException)
            {
                //Context.Instance.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                //Context.Instance.CreateObjectSet<T>().ApplyCurrentValues(entity);
                Context.Instance.Attach(entity);
                Context.SaveChanges();
            }
            catch (UpdateException)
            {
                Context.Instance.Detach(entity);
                throw;
            }
        }

        public void Detach()
        {
            //   Context.Instance.DeleteObject(this);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete()
        {
            if (GetEntityState() == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            Context.Delete(Entity);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete(EntityObject entity)
        {
            if (entity.EntityState == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            Context.Delete(entity);
        }

        /// <summary>
        /// Deletes object 
        /// </summary>
        public void Delete<T>(int id) where T : class
        {
            var entity = GetContextInstance<T>().FirstOrDefault(e => GetProperty<int>(e, Id) == id);

            if (entity == null) return;

            Context.Delete(entity);
        }

        /// <summary>
        /// Inatives object 
        /// </summary>
        public void Inative()
        {
            if (GetEntityState() == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            if (!ExistsProperty(Entity.GetType(), Active))
            {
                Delete();
                return;
            }

            Entity.GetType().GetProperty(Active).SetValue(Entity, false, null);
            Save();
        }

        public void Inative(GeneralAction entity)
        {
            if (entity.EntityState == EntityState.Detached)
                throw new EntityException("Entity is not attached");

            if (entity.GetType().GetProperty(Active) == null)
            {
                Delete(entity);
                return;
            }

            entity.GetType().GetProperty(Active).SetValue(entity, false, null);
            Save(entity);
        }

        /// <summary>
        /// Select from database by Id. If the entity doesn't have the Id property will occur an Exception
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <param name="id">Id</param>
        /// <returns>Entity by Id</returns>
        public T SelectById<T>(int id) where T : new()
        {
            if (!ExistsProperty(typeof(T), Id))
                throw new Exception("Do not exist the property");

            return
                (from T entity in GetContextInstance<T>()
                 where GetProperty<int>(entity, Id) == id
                 select entity).FirstOrDefault();
        }

        /// <summary>
        /// Select from database by Id. If the entity doesn't have the Id property will occur an Exception
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <param name="id">Id</param>
        /// <returns>Entity active by Id</returns>
        public T SelectActiveById<T>(int id) where T : new()
        {
            if (!ExistsProperty(typeof(T), Active) || !ExistsProperty(typeof(T), Id))
                throw new Exception("Do not exist the property");

            return
                (from T entity in GetContextInstance<T>()
                 where GetProperty(entity, Id).Equals(id)
                       && GetProperty(entity, Active).Equals(true)
                 select entity).FirstOrDefault();
        }

        /// <summary>
        /// Select all from database
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>All entities</returns>
        public static List<T> SelectAll<T>()
        {
            return (from T entity in GetContextInstance<T>() select entity).ToList();
        }

        /// <summary>
        /// Select all active from database
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>All Actives entities</returns>
        public List<T> SelectAllActive<T>()
        {
            if (ExistsProperty(typeof(T), Active))
                return (from T entity in GetContextInstance<T>()
                        where GetProperty(entity, Active).Equals(true)
                        select entity).ToList();

            return SelectAll<T>();
        }

        public T CopyEntity<T>(T entity, bool copyKeys = false) where T : GeneralAction
        {
            T clone = Context.Instance.CreateObject<T>();
            PropertyInfo[] pis = entity.GetType().GetProperties();

            foreach (PropertyInfo pi in pis)
            {
                EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])
                              pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);

                foreach (EdmScalarPropertyAttribute attr in attrs)
                {
                    if (!copyKeys && attr.EntityKeyProperty)
                        continue;

                    pi.SetValue(clone, pi.GetValue(entity, null), null);
                }
            }

            return clone;
        }

        #endregion

        #region Private Methods

        private object GetProperty(object entity, string propertyName)
        {
            return entity.GetType().GetProperty(propertyName).GetValue(entity, null);
        }

        private T GetProperty<T>(object entity, string propertyName)
        {
            // var c = new Converter<object, T>(input => (T)Convert.ChangeType(input, typeof(T)));
            // return new Converter(). <object, T>(entity.GetType().GetProperty(propertyName).GetValue(entity, null), c));

            //return new Converter<int, T>(input => (T)Convert.ChangeType(input, typeof(T)));

            //return entity.GetType().GetProperty(propertyName).GetValue(entity, null);
            return (T)Convert.ChangeType(entity.GetType().GetProperty(propertyName).GetValue(entity, null), typeof(T));
        }

        /// <summary>
        /// Get EF instance
        /// </summary>
        /// <typeparam name="T">Entity type</typeparam>
        /// <returns>Database Container</returns>
        private static List<T> GetContextInstance<T>()
        {
            try
            {
                return ((IEnumerable<T>)Context.Instance.GetType().GetProperty(GetEntityName(typeof(T))).GetValue(Context.Instance, null)).ToList();
            }
            catch (ArgumentException)
            {
                return ((IEnumerable<T>)Context.Instance.GetType().GetProperty(GetEntityName(typeof(T))).GetValue(Context.Instance, null)).ToList();
            }
        }

        /// <summary>
        /// Verify if exists a property in entity
        /// </summary>
        /// <param name="property">Property name</param>
        private static bool ExistsProperty(Type entity, string property)
        {
            return entity.GetProperty(property) != null;
            //throw new EntityException(string.Concat("The entity ", new T().GetType().Name, " does not have the property ", property));
        }

        /// <summary>
        /// Gets entity state from local entity
        /// </summary>
        /// <returns>Local entity state</returns>
        private EntityState GetEntityState()
        {
            PropertyInfo propertyInfo = Entity.GetType().GetProperty("EntityState");
            return (EntityState)propertyInfo.GetValue(Entity, null);
        }

        /// <summary>
        /// Gets entity name. Pluralized if is need.
        /// </summary>
        /// <returns>Entity name</returns>
        private static string GetEntityName(Type entityType)
        {
            return IsPluralized
                ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(entityType.Name)
                : entityType.Name;
        }

        //private string GetEntityName()
        //{
        //    return IsPluralized
        //        ? PluralizationService.CreateService(new CultureInfo("EN-US")).Pluralize(Entity.GetType().Name)
        //        : Entity.GetType().Name;
        //}

        public object SelectJoin<T>()
        {
            var test = (from teste in GetContextInstance<T>()
                        join teste2 in GetContextInstance<T>() on GetProperty(teste, "id") equals
                            GetProperty(teste2, "idTeste")
                        select new { });

            return null;
        }

        public object Join<T>(IEnumerable<T> entityBase)
        {
            //var results = Enumerable.Join(entityBase,
            //            teste => GetProperty(teste, "id"),
            //            other => GetProperty(other, "id"),
            //            (teste, other) => new
            //            {
            //                MissingField = other.Field,
            //                OtherMissingField = other.OtherField
            //            });
            return from s in GetContextInstance<T>()
                   join h in GetContextInstance<T>() on Teste(s) equals Teste2(h)
                   select s;
        }

        private object Teste(object linq)
        {
            return new { GetProperty = GetProperty(linq, "id") };
        }
        private object Teste2(object linq)
        {
            return new { GetProperty = GetProperty(linq, "id") };
        }

        #endregion

        //#region EF BulkCopy

        // public void BulkInsertAll<T>(T[] entities) where T : GeneralAction
        //{
        //    var conn = (SqlConnection)Context.Instance.Connection;

        //    conn.Open();
            
        //    Type t = typeof(T);
        //    //Set(t).ToString();
        //    var objectContext = Context.Instance;
        //    var workspace = objectContext.MetadataWorkspace;
        //    var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        //    var tableName = GetTableName<T>();
        //    var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName};

        //    // Foreign key relations show up as virtual declared 
        //    // properties and we want to ignore these.
        //    var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        //    var table = new DataTable();
        //    foreach (var property in properties)
        //    {
        //        Type propertyType = property.PropertyType;

        //        // Nullable properties need special treatment.
        //        if (propertyType.IsGenericType &&
        //            propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
        //        {
        //            propertyType = Nullable.GetUnderlyingType(propertyType);
        //        }

        //        // Since we cannot trust the CLR type properties to be in the same order as
        //        // the table columns we use the SqlBulkCopy column mappings.
        //        table.Columns.Add(new DataColumn(property.Name, propertyType));
        //        var clrPropertyName = property.Name;
        //        var tableColumnName = mappings[property.Name]; 
        //        bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        //    }

        //    // Add all our entities to our data table
        //    foreach (var entity in entities)
        //    {
        //        var e = entity;
        //        table.Rows.Add(properties.Select(property => 
        //           GetPropertyValue(property.GetValue(e, null))).ToArray());
        //    }

        //    // send it to the server for bulk execution
        //    bulkCopy.WriteToServer(table);

        //    conn.Close();
        //}

        //private string GetTableName<T>() where T : GeneralAction
        //{
        //    var dbSet = Context.Instance.GetType().GetProperty(GetEntityName(typeof(T))).GetValue(Context.Instance, null);
        //    var sql = dbSet.ToString();
        //    var regex = new Regex(@"FROM (?.*) AS");
        //    var match = regex.Match(sql);
        //    return match.Groups["table"].Value;
        //}

        //private object GetPropertyValue(object o)
        //{
        //    if (o == null)
        //        return DBNull.Value;
        //    return o;
        //}

        //private Dictionary<string,> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
        //{
        //    var mappings = new Dictionary<string,>();
        //    var storageMapping = workspace.GetItem<globalitem>(containerName, DataSpace.CSSpace);
        //    dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
        //        "EntitySetMaps",
        //        BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
        //        null, storageMapping, null);

        //    foreach (var entitySetMap in entitySetMaps)
        //    {
        //        var typeMappings = GetArrayList("TypeMappings", entitySetMap);
        //        dynamic typeMapping = typeMappings[0];
        //        dynamic types = GetArrayList("Types", typeMapping);

        //        if (types[0].Name == entityName)
        //        {
        //            var fragments = GetArrayList("MappingFragments", typeMapping);
        //            var fragment = fragments[0];
        //            var properties = GetArrayList("AllProperties", fragment);
        //            foreach (var property in properties)
        //            {
        //                var edmProperty = GetProperty("EdmProperty", property);
        //                var columnProperty = GetProperty("ColumnProperty", property);
        //                mappings.Add(edmProperty.Name, columnProperty.Name);
        //            }
        //        }
        //    }

        //    return mappings;
        //}

        //private ArrayList GetArrayList(string property, object instance)
        //{
        //    var type = instance.GetType();
        //    var objects = (IEnumerable)type.InvokeMember(property, BindingFlags.GetProperty | 
        //      BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
        //    var list = new ArrayList();
        
        //    foreach (var o in objects)
        //        list.Add(o);
        
        //    return list;
        //}

        //private dynamic GetProperty(string property, object instance)
        //{
        //    var type = instance.GetType();
        //    return type.InvokeMember(property, BindingFlags.GetProperty | 
        //      BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
        //}

        //#endregion
    }
}
